/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.yonsei.iwb.srf;

import edu.yonsei.iwb.sdt.SDTLearningType;
import edu.yonsei.iwb.sdt.datastructure.TargetInstance;
import edu.yonsei.iwb.sdt.datastructure.tree.DEdge;
import edu.yonsei.iwb.sdt.datastructure.tree.DNode;
import edu.yonsei.iwb.sdt.datastructure.tree.NodeType;
import edu.yonsei.iwb.sdt.ontology.PropertyArc;
import edu.yonsei.iwb.sdt.refinement.CardinalityRestrictionRefinement;
import edu.yonsei.iwb.sdt.refinement.ConceptConstructorRefinement;
import edu.yonsei.iwb.sdt.refinement.ConjunctionRefinement;
import edu.yonsei.iwb.sdt.refinement.DLConstructors;
import edu.yonsei.iwb.sdt.refinement.DisjunctionRefinement;
import edu.yonsei.iwb.sdt.refinement.DomainRestrictionRefinement;
import edu.yonsei.iwb.sdt.refinement.ExpandingHistory;
import edu.yonsei.iwb.sdt.refinement.QualificationRefinement;
import edu.yonsei.iwb.sdt.refinement.Refinement;
import edu.yonsei.iwb.sdt.refinement.RefinementType;
import edu.yonsei.iwb.sdt.refinement.ValueInfoOfDP;
import edu.yonsei.iwb.sdt.util.SDTLog;
import edu.yonsei.iwb.sdt.util.UtilFunctions;
import edu.yonsei.iwb.srf.server.TestClient;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;

/**
 *
 * @author dk
 */
public class BuildForestDriver {

    private int nbTree;
    private double refSR;
    private String targetClass;
    private String targetProperty;
    private String runID;
    private String inputDataLoc;
    private int tisize;
    
    private int featureSelectionType;
    private boolean searchMethod;
    private ArrayList<String> slaveList;
//    private ArrayList<String> inslist;
    private ArrayList<Integer> nbes;
//    private ArrayList<Hashtable<Integer, ArrayList<String>>> tiset;
//    private ArrayList<String> valueSet;
    private String[] indexName;
//    private TIBootstrapper tib;

    public BuildForestDriver(String inputDataLoc, String runID, String[] indexList, String tc, String tp, int numberOfTree, double refSelectRate, ArrayList<String> slList, int tisize, int featureSelectionType, boolean searchMethod) throws IOException {
        this.runID = runID;
        this.inputDataLoc = inputDataLoc;
        this.targetClass = tc;
        this.targetProperty = tp;
        this.refSR = refSelectRate;
        this.nbTree = numberOfTree;
        this.tisize = tisize;
        this.slaveList = slList;
        this.indexName = indexList;
        this.featureSelectionType = featureSelectionType;
        this.searchMethod = searchMethod;                
//        this.inslist = getTargetInstances();
//        this.tib = new TIBootstrapper(indexList, tc, tp);
//        this.tib = new TIBootstrapper(indexList, inputDataLoc);
//        this.tiset = this.tib.getInstanceListOfEachSlave(slList.size(), tisize);
        this.getTreeNumberOfEachSlave();
    }

    public static void main(String[] args) throws IOException {
        String dataLocID = "test_los2";
        String name = "test_los2";
        String tc = "http://www.test.owl#Target";
        String tp = "http://www.test.owl#diseased";
        String indexname = "test_los2";
        
//        String dataLocID = "forte_test";
//        String name = "forte";
//        String tc = "http://localhost/foo#Person";
//        String tp = "http://localhost/foo#isUncle";
//        String indexname = "forte";

//        String inputDataLoc = "family1";
//        String name = "family";
//        String tc = "http://www.co-ode.org/roberts/family-tree.owl#Person";
//        String tp = "http://www.co-ode.org/roberts/family-tree.owl#isUncle";
//        String indexname ="family2";

//        String inputDataLoc = "family_a1";
//        String name = "family_a";
//        String tc = "http://www.co-ode.org/roberts/family-tree.owl#Person";
//        String tp = "http://www.co-ode.org/roberts/family-tree.owl#isAunt";
//        String indexname ="family2";
//        
//        String inputDataLoc = "media1";
//        String name = "media";
//        String tc = "http://iweb.yonsei.ac.kr/ontologies/smarttv/Media";        
//        String tp = "http://iweb.yonsei.ac.kr/ontologies/smarttv/targetproperty";
//        String indexname ="smarttv";
        
        int numberOfTree = 1;
        double refSelectRate = 1.0;
        int tisize = -1;
        int featureSelectionType = SDTLearningType.FS3;
        boolean searchMethod = SDTLearningType.OVERSEARCH;
        
//        boolean featureSelection = true;        
        String runID = name + numberOfTree;
        String[] indexlist = new String[]{indexname};
        ArrayList<String> slavelist = new ArrayList();
        slavelist.add("localhost");
//        slavelist.add("165.132.123.181");
//        slavelist.add("165.132.123.164");
//        slavelist.add("165.132.123.181");

        BuildForestDriver bfd = new BuildForestDriver(dataLocID, runID, indexlist, tc, tp, numberOfTree, refSelectRate, slavelist, tisize, featureSelectionType, searchMethod);
        
//        do data partition
//        bfd.requestDataPartition(0.8, dataLocID);
        
//        generating random forest
        HashMap<String, ArrayList<DNode>> resultTrees = bfd.requestTree();

        for (int i = 0; i < slavelist.size(); i++) {
            String slaveip = slavelist.get(i);
            ArrayList<DNode> treeOfSlave = resultTrees.get(slaveip);
            for (int j = 0; j < treeOfSlave.size(); j++) {
                DNode dn = treeOfSlave.get(j);
                System.out.println("#" + dn.NODE_ID + "------------------------------------");
//                bfd.printNode(dn);
                UtilFunctions.showTree(dn);
            }
        }
//        bfd.getInstanceListOfEachSlave(3, 110);
    }

    public void requestDataPartition(double rate, String dataLocID) {
        ArrayList<Socket> connectionList = new ArrayList();
        for (int i = 0; i < slaveList.size(); i++) {
            String slave = slaveList.get(i);
            try {
                Socket socket = new Socket(slave, 8080);
                PrintWriter pw = new PrintWriter(socket.getOutputStream());
                String request = requestDataPartitionMsg(i, rate, dataLocID);
                pw.println(request);
                pw.flush();
                connectionList.add(socket);
            } catch (UnknownHostException ex) {
                Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        for (int i = 0; i < connectionList.size(); i++) {
            Socket socket = connectionList.get(i);
            BufferedReader br;
            try {
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String line = null;
                StringBuilder sb = new StringBuilder();
                while ((line = br.readLine()) != null) {
                    sb.append(line).append("\n");
                }
                String resultMsg = sb.toString();
//                interpretDataPartitionResult(resultMsg);
                br.close();
                socket.close();
                SDTLog.log(SDTLog.BASIC, "# " + socket.getInetAddress().getHostAddress() + " job(data partition)  complete\n");
            } catch (IOException ex) {
                Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public HashMap<String, ArrayList<DNode>> requestTree() {
        HashMap<String, ArrayList<DNode>> allResultSet = new HashMap();
        ArrayList<Socket> connectionList = new ArrayList();

        for (int i = 0; i < slaveList.size(); i++) {
            String slave = slaveList.get(i);
            try {
                Socket socket = new Socket(slave, 8080);
                PrintWriter pw = new PrintWriter(socket.getOutputStream());
                String request = requestSDTLearnMsg(i);
                pw.println(request);
                pw.flush();
                connectionList.add(socket);
            } catch (UnknownHostException ex) {
                Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        for (int i = 0; i < connectionList.size(); i++) {
            Socket socket = connectionList.get(i);
            BufferedReader br;
            try {
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String line = null;
                StringBuilder sb = new StringBuilder();
                while ((line = br.readLine()) != null) {
                    sb.append(line).append("\n");
                }

                String resultMsg = sb.toString();

                printOut(i, resultMsg);
                ArrayList<DNode> result = interpretSDTLearningResult(resultMsg);

                allResultSet.put(socket.getInetAddress().getHostAddress(), result);

                br.close();
                socket.close();
                SDTLog.log(SDTLog.BASIC, "# " + socket.getInetAddress().getHostAddress() + " job(tree learning) complete\n");
            }catch(java.lang.NullPointerException npe){
                System.out.println("   $# results are empty");
            }catch (IOException ex) {
                Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        return allResultSet;
    }

    private String requestDataPartitionMsg(int slaveNum, double rate, String dataLocID) {
        String result = new String();
        try {
            XContentBuilder doc = XContentFactory.jsonBuilder().prettyPrint().startObject();
//            doc.field("slave", slaveNum);
            doc.field("treenum", this.nbes.get(slaveNum));
            TIPartitional tip = new TIPartitional(this.targetClass, this.targetProperty, this.indexName, rate, dataLocID);
            tip.initialize();

            String trainingMsg = tip.getTrainingMsg();
            String testMsg = tip.getTestMsg();
            String valueMsg = tip.getValueMsg();

            doc.field("-m", "dp");
            doc.field("training", trainingMsg);
            doc.field("test", testMsg);
            doc.field("value", valueMsg);
            doc.field("dataloc", dataLocID);
            doc.endObject();
            result += doc.string();

        } catch (IOException ex) {
            Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            return result;
        }
    }

    private String requestSDTLearnMsg(int slaveNum) {
        String result = new String();
        try {
            XContentBuilder doc = XContentFactory.jsonBuilder().prettyPrint().startObject();
            doc.field("slave", slaveNum);
            doc.field("treenum", this.nbes.get(slaveNum));

//            doc.field("insset", insset);
            doc.field("-m", "sdt");
            doc.field("refsr", this.refSR);
            doc.field("tc", this.targetClass);
            doc.field("tp", this.targetProperty);
            doc.field("runID", this.runID);
            doc.field("tisize", this.tisize);
            doc.field("indexname", this.indexName);
            doc.field("dataloc", this.inputDataLoc);
            doc.field("fstype",this.featureSelectionType);
            doc.field("searchMethod", this.searchMethod);
            doc.endObject();
            result += doc.string();

        } catch (IOException ex) {
            Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            return result;
        }
    }

    private void interpretDataPartitionResult(String result) throws IOException {
//        XContentParser jsonResult = JsonXContent.jsonXContent.createParser(result);
//        Map<String, Object> resultMap = jsonResult.mapOrdered();
//        String msg = resultMap.get("msg").toString();
        System.out.println("# " + result);
    }

    private ArrayList<DNode> interpretSDTLearningResult(String result) throws IOException {
        ArrayList<DNode> resultTreeSet = new ArrayList();
//        System.out.println("--"+result);
        XContentParser jsonResult = JsonXContent.jsonXContent.createParser(result);
        Map<String, Object> resultMap = jsonResult.mapOrdered();
        Map<String, Object> set = (Map<String, Object>) resultMap.get("set");
        String slavenum = set.get("slavenum").toString();
        String treeSize = set.get("size").toString();

        System.out.println("# treesize : " + treeSize);
        System.out.println("# slavenum : " + slavenum);

        for (int j = 0; j < Integer.parseInt(treeSize); j++) {
            LinkedHashMap treeinfo = (LinkedHashMap) set.get("id" + slavenum + "_" + j);
            String rootid = treeinfo.get("rootid").toString();
            Hashtable<String, DNode> nodeTable = new Hashtable();
            ArrayList<String> nodelist = (ArrayList<String>) treeinfo.get("nodelist");
            for (int i = 0; i < nodelist.size(); i++) {
                String nodeIDinJSON = nodelist.get(i);
                LinkedHashMap node = (LinkedHashMap) treeinfo.get(nodeIDinJSON);
                String nodeid = node.get("nodeid").toString();
                double entropy = (double) node.get("entropy");
                String noderef = node.get("noderef").toString();

//                ArrayList<String> tiIDs = (ArrayList<String>) node.get("tiIDs");
//                ArrayList<String> tiURIs = (ArrayList<String>) node.get("tiURIs");
//                ArrayList<Integer> tiValues = (ArrayList<Integer>) node.get("tiValues");
//                Vector<TargetInstance> nodeTIset = new Vector();
//
//                for (int k = 0; k < tiIDs.size(); k++) {
//                    String id = tiIDs.get(k);
//                    String uri = tiURIs.get(k);
//                    int value = tiValues.get(k);
//                    TargetInstance ti = new TargetInstance(id, uri, value);
//                    nodeTIset.add(ti);
//                }

//                DNode dn = new DNode(nodeTIset, noderef, Integer.parseInt(nodeid), entropy);
                DNode dn = new DNode(noderef, Integer.parseInt(nodeid), entropy);
                dn.NODE_TYPE = (int) node.get("nodetype");
                if (dn.NODE_TYPE == NodeType.LEAFNODE) {
                    int classLabel = (int) node.get("nodeclass");
                    dn.setFinalClassLabel(classLabel);
                }
                dn.setTargetValueInstanceInformation();
                nodeTable.put(nodeIDinJSON, dn);
            }

            for (int i = 0; i < nodelist.size(); i++) {
                String nodeid = nodelist.get(i);
                LinkedHashMap node = (LinkedHashMap) treeinfo.get(nodeid);
                int cnsize = (int) node.get("cnsize");
                //child node 정보로 edge 생성...
                for (int k = 0; k < cnsize; k++) {
                    String childID = "child_" + k;
                    LinkedHashMap childNodeInfo = (LinkedHashMap) node.get(childID);
                    int cnid = (int) childNodeInfo.get("cnid");
                    boolean branchType = (boolean) childNodeInfo.get("edgetype");
                    String refname = (String) childNodeInfo.get("refname");

                    Refinement ref = getRefinementInfo(childNodeInfo, branchType);
                    ref.setBranchingType(branchType);
                    ref.setRefinementname(refname);

                    DNode parentNode = nodeTable.get(nodeid);
                    DNode childNode = nodeTable.get("n_" + cnid);
                    DEdge de = new DEdge(parentNode, childNode, ref);
                    parentNode.setEdge(de);
                }
            }

            resultTreeSet.add(nodeTable.get("n_" + rootid));
        }




        return resultTreeSet;
    }

    private Refinement getRefinementInfo(LinkedHashMap childNodeInfo, boolean branchType) {
        int type = (int) childNodeInfo.get("reftype");
        boolean expanded = (boolean) childNodeInfo.get("expanded");
        Refinement ref = null;
        ExpandingHistory eh;
        if (expanded) {
//            System.out.println("--**********************    "+childNodeInfo.get("qset").getClass());
            ArrayList<String> qseta = (ArrayList<String>) childNodeInfo.get("qset");
            Vector<String> qset = new Vector(qseta);
//            Vector<String> qset = (Vector<String>) childNodeInfo.get("qset");
            Vector<PropertyArc> paset = new Vector();
            ArrayList<String> pset = (ArrayList<String>) childNodeInfo.get("pset");
            ArrayList<Boolean> pdset = (ArrayList<Boolean>) childNodeInfo.get("pdset");
            for (int l = 0; l < pdset.size(); l++) {
                String property = pset.get(l);
                boolean direction = pdset.get(l);
                paset.add(new PropertyArc(property, direction));
            }
            eh = new ExpandingHistory(paset, qset);

            if (type == RefinementType.CONJUNCTION) {
                String cen = childNodeInfo.get("cen").toString();
                String con = childNodeInfo.get("con").toString();
                if (branchType) {
                    ref = new ConjunctionRefinement("", eh, cen, con);
                } else {
                    ref = new ConjunctionRefinement(DLConstructors.NEGATION, eh, cen, con);
                    ref.setBranchingType(branchType);
                }
            } else if (type == RefinementType.DISJUNCTION) {
                String cen = childNodeInfo.get("cen").toString();
                String con = childNodeInfo.get("con").toString();
                if (branchType) {
                    ref = new DisjunctionRefinement("", eh, cen, con);
                } else {
                    ref = new DisjunctionRefinement(DLConstructors.NEGATION, eh, cen, con);
                    ref.setBranchingType(branchType);
                }
            } else {
                if (type == RefinementType.CARDINALITYRESTRICTION) {
                    String property = childNodeInfo.get("property").toString();
                    boolean pdir = (boolean) childNodeInfo.get("pdir");
                    PropertyArc pa = new PropertyArc(property, pdir);
                    int standard = (int) childNodeInfo.get("standard");
                    if (branchType) {
                        ref = new CardinalityRestrictionRefinement(standard, ">", pa, eh);
                    } else {
                        ref = new CardinalityRestrictionRefinement(standard, "<=", pa, eh);
                    }
                } else if (type == RefinementType.QUALIFICATION) {
                    String cen = childNodeInfo.get("cen").toString();
                    if (branchType) {
                        ref = new QualificationRefinement("", eh, cen);
                    } else {
                        ref = new QualificationRefinement(DLConstructors.NEGATION, eh, cen);
                    }
                } else if (type == RefinementType.DOMAINRESTRICTION) {
                    int dtype = (int) childNodeInfo.get("dtype");
                    String property = childNodeInfo.get("property").toString();
                    if (dtype == ValueInfoOfDP.DATARANGE) {
                        if (branchType) {
                            String standard = childNodeInfo.get("standard").toString();
                            ref = new DomainRestrictionRefinement(standard, "==", eh, property, dtype);
                        } else {
                            Vector<String> datarange = (Vector<String>) childNodeInfo.get("datarange");
                            ref = new DomainRestrictionRefinement(datarange, eh, property);
                        }
                    } else {
                        String prefix = childNodeInfo.get("prefix").toString();
                        String standard = childNodeInfo.get("standard").toString();
                        ref = new DomainRestrictionRefinement(standard, prefix, eh, property, dtype);
                    }
                }

            }
        } else {
            // not expanded
            if (type == RefinementType.CONCEPTCONSTRUCTOR) {
                if (branchType) {
                    boolean multi = (boolean) childNodeInfo.get("multi");
                    if (multi) {
                        ArrayList<String> subset = (ArrayList<String>) childNodeInfo.get("subset");
                        ref = new ConceptConstructorRefinement(subset);
                    } else {
                        String con = childNodeInfo.get("con").toString();
                        ref = new ConceptConstructorRefinement(DLConstructors.NEGATION, con);
                    }
                } else {
                    String con = childNodeInfo.get("con").toString();
                    ref = new ConceptConstructorRefinement("", con);
                }
            } else if (type == RefinementType.CONJUNCTION) {
                String cen = childNodeInfo.get("cen").toString();
                String con = childNodeInfo.get("con").toString();
                if (branchType) {
                    ref = new ConjunctionRefinement("", cen, con);
                } else {
                    ref = new ConjunctionRefinement(DLConstructors.NEGATION, cen, con);
                }
            } else if (type == RefinementType.DISJUNCTION) {
                String cen = childNodeInfo.get("cen").toString();
                String con = childNodeInfo.get("con").toString();
                if (branchType) {
                    ref = new DisjunctionRefinement("", cen, con);
                } else {
                    ref = new DisjunctionRefinement(DLConstructors.NEGATION, cen, con);
                }
            } else {
                if (type == RefinementType.CARDINALITYRESTRICTION) {
                    String property = childNodeInfo.get("property").toString();
                    boolean pdir = (boolean) childNodeInfo.get("pdir");
                    PropertyArc pa = new PropertyArc(property, pdir);
                    int standard = (int) childNodeInfo.get("standard");
                    if (branchType) {
                        ref = new CardinalityRestrictionRefinement(standard, ">", pa);
                    } else {
                        ref = new CardinalityRestrictionRefinement(standard, "<=", pa);
                    }
                } else if (type == RefinementType.DOMAINRESTRICTION) {
                    int dtype = (int) childNodeInfo.get("dtype");
                    String property = childNodeInfo.get("property").toString();
                    if (dtype == ValueInfoOfDP.DATARANGE) {
                        if (branchType) {
                            String standard = childNodeInfo.get("standard").toString();
                            ref = new DomainRestrictionRefinement(standard, "==", property, dtype);
                        } else {
                            Vector<String> datarange = (Vector<String>) childNodeInfo.get("datarange");
                            ref = new DomainRestrictionRefinement(datarange, property);
                        }
                    } else {
                        String prefix = childNodeInfo.get("prefix").toString();
                        String standard = childNodeInfo.get("standard").toString();
                        ref = new DomainRestrictionRefinement(standard, prefix, property, dtype);
                    }
                }
            }
        }
        return ref;
    }

    /**
     * get the number of trees for each server
     *
     * @return
     */
    private void getTreeNumberOfEachSlave() {
        int nbSlave = this.slaveList.size();
        this.nbes = new ArrayList();
        int nbTrees = this.nbTree / nbSlave; // 노드당 트리 개수        
        int remains = this.nbTree - nbTrees * nbSlave;
        int first = nbTrees + remains;

        this.nbes.add(0, first);
        for (int i = 1; i < this.slaveList.size(); i++) {
            this.nbes.add(nbTrees);
        }

    }

    private void printOut(int slavenum, String resultMsg) {
        PrintWriter pwtj = null;
        try {
            File dir = new File("./trees/master/" + runID);
            dir.mkdirs();
            File tfile = new File("./trees/master/" + runID + "/" + slavenum + "_trees.json");
            tfile.createNewFile();
            pwtj = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tfile), "euc-kr"));
            pwtj.write(resultMsg);
            pwtj.flush();
            pwtj.close();
        } catch (UnsupportedEncodingException ex) {
            Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            pwtj.close();
        }
    }

    public void printNode(DNode node) {
//        String s = new String();        
        for (int i = 0; i < node.getEdge().size(); i++) {
            DEdge edge = node.getEdge().get(i);

            SDTLog.log(SDTLog.BASIC, edge.toString());

//            edge.getChildNode().setRuleComposer(node.getRuleComposer(), edge.getRefinement());  // rule generate

            if (edge.getChildNode().NODE_TYPE == NodeType.BRANCHNODE) {
                SDTLog.log(SDTLog.BASIC, edge.getChildNode().toString());
                printNode(edge.getChildNode());
            } else {
                SDTLog.log(SDTLog.BASIC, edge.getChildNode().toString());
            }
        }
    }
}
